/**
 * 
 */
package edu.sjtu.se.prophet.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import edu.sjtu.se.prophet.graph.Edge;
import edu.sjtu.se.prophet.graph.Graph;
import edu.sjtu.se.prophet.graph.Node;
import edu.sjtu.se.prophet.utils.Pair;

/**
 * @author willard
 * 
 */
public class DotFileBucketWriter implements GraphWriter {

    private static Logger logger = Logger.getLogger(DotFileBucketWriter.class);

    public static final String ARFF_FILE = "data.arff.file";
    public static final String DOT_FILE = "data.dot.file";
    public static final String START_RED = "out.start.red";
    public static final String START_GREEN = "out.start.green";
    public static final String START_BLUE = "out.start.blue";
    public static final String END_RED = "out.end.red";
    public static final String END_GREEN = "out.end.green";
    public static final String END_BLUE = "out.end.blue";

    private static final String LEVEL = "Level";
    private static final String GROUP = "Group";

    private float startRed = 1;
    private float startGreen = 1;
    private float startBlue = 1;
    private float endRed = 0;
    private float endGreen = 0;
    private float endBlue = 0;
    private String arffFileName = "data.arff";
    private String dotFileName = "data.dot";

    protected List<Graph> graphs;

    private Map<String, List<Pair<Integer, Integer>>> groupLevels;


    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.GraphWriter#init(java.util.List)
     */
    @Override
    public void init(List<Graph> graphs) {
        this.graphs = graphs;
    }


    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.GraphWriter#setValue(java.lang.String, java.lang.String)
     */
    @Override
    public void setValue(String key, String value) {
        if (START_RED.equals(key)) {
            startRed = Float.parseFloat(value);
        } else if (START_GREEN.equals(key)) {
            startGreen = Float.parseFloat(value);
        } else if (START_BLUE.equals(key)) {
            startBlue = Float.parseFloat(value);
        } else if (END_RED.equals(key)) {
            endRed = Float.parseFloat(value);
        } else if (END_GREEN.equals(key)) {
            endGreen = Float.parseFloat(value);
        } else if (END_BLUE.equals(key)) {
            endBlue = Float.parseFloat(value);
        } else if (ARFF_FILE.equals(key)) {
            arffFileName = value;
        } else if (DOT_FILE.equals(key)) {
            dotFileName = value;
        }
    }


    /*
     * (non-Javadoc)
     * 
     * @see edu.sjtu.se.prophet.io.GraphWriter#wirte()
     */
    @Override
    public void wirte() throws IOException {
        logger.info("Color bounds: (" + startRed + ", " + startGreen + ", " + startBlue + ") - (" + endRed + ", "
                + endGreen + ", " + endBlue + ")");
        logger.info("ARFF data file: " + arffFileName);

        logger.info("DOT data file: " + dotFileName);

        generate();
        flush();
    }


    private void flush() throws IOException {
        for (Graph graph : graphs) {
            List<Pair<Integer, Integer>> levels = groupLevels.get(graph.getMeta().get(IOCommon.VERSION));
            Collections.sort(levels, new Comparator<Pair<Integer, Integer>>() {

                @Override
                public int compare(Pair<Integer, Integer> o1, Pair<Integer, Integer> o2) {
                    return o1.getRight() - o2.getRight();
                }
            });
            int num = levels.size();
            Map<Integer, String> colorTable = new HashMap<Integer, String>();
            for (int i = 0; i < levels.size(); i++) {
                float ratio = ((float) i) / num;
                float red = getColor(startRed, endRed, ratio);
                float green = getColor(startGreen, endGreen, ratio);
                float blue = getColor(startBlue, endBlue, ratio);
                colorTable.put(levels.get(i).getLeft(), "\"" + red + " " + green + " " + blue + "\"");
            }
            BufferedWriter bw = new BufferedWriter(
                    new FileWriter(dotFileName));
            bw.write("digraph G {\n");
            for (Node node : graph.getNodes()) {
                Integer group = (Integer) node.getMetrics().get(GROUP);
                bw.write("\"" + node.getId() + "\" [style=filled,color=" + colorTable.get(group) + "];\n");
            }

            for (Edge edge : graph.getEdges()) {
                bw.write("\"" + edge.getStart().getId() + "\" -> \"" + edge.getEnd().getId() + "\";\n");
            }
            bw.write("}\n");
            bw.close();
        }
    }


    private float getColor(float start, float end, float ratio) {
        if (start < 0)
            start = 0;
        if (end > 1)
            end = 1;
        if (ratio > 1)
            ratio = 1;
        if (ratio < 0)
            ratio = 0;
        return start + (end - start) * ratio;
    }


    private void generate() throws IOException {
        groupLevels = new HashMap<String, List<Pair<Integer, Integer>>>();
        for (Graph g : graphs) {
            BufferedReader br = new BufferedReader(new FileReader(arffFileName));
            String line = null;
            int group = 1;
            List<Pair<Integer, Integer>> levels = new ArrayList<Pair<Integer, Integer>>();
            while ((line = br.readLine()) != null) {
                if (line.startsWith("@"))
                    continue;
                String[] parts = line.split("%");
                if (parts.length < 2)
                    continue;
                String[] metrics = parts[0].split(",");
                int level = Integer.parseInt(metrics[metrics.length - 1].replace("Level", "").replace(" ", ""));
                String[] functions = parts[1].replace(", ", ",").trim().split(" ");
                for (String func : functions) {
                    if ("".equals(func.trim())) {
                        logger.warn("Empty function name found!");
                    }
                    func = func.replace(",", ", ");
                    Node node = g.findNode(func);
                    if (node != null) {
                        node.getMetrics().put(LEVEL, level);
                        node.getMetrics().put(GROUP, group);
                        levels.add(new Pair<Integer, Integer>(group, level));
                        group++;
                    } else {
                        logger.error("Node not found: " + func);
                    }
                }
            }
            br.close();

            groupLevels.put((String) g.getMeta().get(IOCommon.VERSION), levels);
        }
    }

}
